[声明]欢迎讨论,欢迎一起学习
题目:
将n(一个整数)划分成m个(大于等于1的整数)有多少种可能
例如:
5 2 out: 2 (2,3 ; 1,4);
100 33 out : 2625726
9 3 out:7
//相信大家在网上找也是可以找到很多这种算法的。不过我觉得他们都是大神了,所以他们交流的时候可能很多默认知道的东西,像我这种菜鸡都要想很久
//所以,我写了两个算法,一个是自己想的(比较容易看懂),另一个是看了网上很多贴子(很多代码,才慢慢看懂的)在这里也一并展示给大家;
[算法一]:
工具:三维数组 a[i][j][k] //表示将i 分为j个数,第j个数(即以后)的下限为(大于等于)k
//输出表示 就是 a[n][m][1];
//想到用这个方法其实是很符合逻辑的;想要用到之前已经做好的数据降低时间复杂度,取号一种情况后,之后情况就必须大于等于这个下限
//将n个数(要是不隔开,看起来总让我想起数学分析....)分为m个,为了避免重复计算必须限制下限;
//边界条件是:
i < j*k : a[i][j][k] = 0;//要是最小的情况都放不了了,那么不是0是什么呢?
i == j*k : a[i][j][k] = 1;//要是最小的情况,只能每个都放一样的,不然就不能放进去,只有一种情况
j ==1 && i >= k : a[i][j][k] =1;//只分为一个数,只有一种情况,但必须要求 i >= k,否则的话也是为0的
//递推公式是:(前面都已经筛选了这么多种情况了)
只剩下这个
if i > j*k : a[i][j][k] = sum{a[i-t][j-1][t]} (t == k,k+1,...,i/j)
//这是关于第j个数的选择,它至少是k,由于定义,所以 t 从k开始遍历
//遍历结束的位置是 i/j同k的,这看上去是一个比较粗糙的限制。但是当(i-t) >= (j-1)*t => i >=j*t => t <= i/j
//所以,这其实是一个刚刚好的限制(要是有大佬告诉一个更好的限制,请在评论区留言,万分感激!)
/* <不懂再来看这个,大佬可以跳过这个>
递推公式:
if(i>j*k) a[i][j][k] = sum{a[i-t][j-1][t]}; t= k,k+1,...,i/j;
将i分为j个数下限为k,就是其实在遍历最小的那个数(t),t可以是k,k+1,k+2,...,floor(i/j);
边界条件:(由于我们一开始就已经将所有的值设置为了0),所以只需要考虑为为非0的边界条件
但是实际上,前面初始化全为0,本来就考虑到为0就是边界条件的一种,所以也是要考虑的
只不过减少了代码书写方面的考虑
那么非0的边界是什么呢?
首先j==1&& i>=k时为1,这个很明显,最小为1,必须有a[i][j][k] == 1;
其次,i==j*k时候 那么就是刚好只有一种情况 a[i][j][k] == 1;
分两种情况讨论,i能被j整除时,k肯定要小于等于(i/j),因为k是下限,不然就会超过i
i不能被j整除时,k*j < i <(k+1)*j,所以,k最大也只能是(i/j),因此,k的数学表达为
k <= floor(i/j),其C++表达就是i/j;
*/
#include <iostream>
#include <cstring>
using namespace std;
int a[101][101][101];
void f(int n,int m){
memset(a,0,sizeof(a));
for (int i=1;i<=n;++i)
for (int j=1;j<=m;++j)//其实j最大不能超过i,否者不能保证每个都是整数了
for (int k=1;k<=(i/j);++k){
if (j==1&&i>=k){
a[i][j][k] = 1;
}else if(i == j*k){
a[i][j][k] = 1;
}else if (i > j*k){
for (int t=k;t<=(i/j);++t){
a[i][j][k] += a[i-t][j-1][t];
}
}
}
cout << a[n][m][1]<<endl;
}
int main(){
int n,m;//当n==m==0时,结束
while(cin>>n>>m&&(n||m)){
f(n,m);
}
}
[算法二]://算是看了网上很多的算法,这里只是做一个解释
//网上关于这个的算法很多,我看了很多之后,自己按照某一种的思路自己打了一个
工具:a[1000][1000];//dp[i][j]是网上大佬们都喜欢用的,我是个菜鸡,我喜欢用简单点的。
a[i][j] 表示将i分为j个数;
//这时候,要是你还是用之前的思路,是不行的,容我细细解析大佬们的思维;
//大局观:首先,这j个数进行考察,首先,要么全都是大于等于2的,否则就有一个是1;
//假如有一个是1,那么就直接将这个数拿出去,就是a[i-1][j-1];
//假如说全都是大于等于2,那么就将每个都拿掉一层1,很明显,拿掉一层其实不影响数量的
//到这里,我们就证明了,为什么 a[i][j] =a[i-j][j]+a[i-1][j-1];
//因为,这里用了关于每个数一个整体的讨论,从而构建了递推公式
//边界条件:
j==1||i==j : a[i][j] = 1;
同时,当i<j a[i][j] =0;//这个通过不处理实现。
//将n个物体分为m个组合
#include<iostream>
#include<cstring>
using namespace std;
int a[1000][1000];
//a[i][j]表示将i这么多的数,分为j个数,有多少种分法
void f(int n,int m){
memset(a,0,sizeof(a));
//边界条件:(将i分为j个数)
//j==1||i==j:a[i][j]==1;
//if(i>j) a[i][j]= a[i-j][j]+a[i-1][j-1];
//讨论的情况是这样的,假如至少存在有一个为1,否者就全都是大于等于二的;
//那么,这时候,就将每个都有的那个1给去掉,剩下的数就再来分成j个有多少就是多少
//如果至少存在有一个是1,那么就将那个去掉后,剩下的,有多少种可能就是有多少种可能;
//相加起来就是我们想要知道的a[i][j]
for(int i=1;i<=n;++i){
for (int j=1;j<=i;++j){//当j>i时,必须为0;
if (j==1||i==j)a[i][j]=1;
else a[i][j] = a[i-j][j]+a[i-1][j-1];
}
}
cout << a[n][m]<<endl;
}
int main(){
int n,m;
while (cin >>n>>m&&n&&m){
f(n,m);
}
}